"use strict";

function ChartingPercentile(chart) {
  this.chart = chart;
  chart.percentile = this;
  this.interval = this.getInterval() + Math.random() * 6000;

  console.log("Recalculating Percentiles every ", this.getInterval(), "ms, First Calculation staggered: ", Math.floor( this.interval - this.getInterval() ), "ms" );
  this.intervalId = window.setInterval(this.zoomToPercentileInterval.bind(this), this.interval);
}

ChartingPercentile.prototype.getPointValue = function( point ) {
  return point;
}

ChartingPercentile.prototype.getInterval = function () {
  return parseFloat(this.chart.getOptionParameter(KEY_PERCENTILE_INTERVAL)) * 1000;
}

ChartingPercentile.prototype.zoomToPercentileInterval = function () {
  var configuredInterval = this.getInterval();
  if (!this.chart || !this.chart.options) {
    console.info("Stopping Zoom Refresh, Chart is being reinitiated.");
    window.clearInterval(this.intervalId);
    return;
  }

  if (this.interval != configuredInterval) {
    window.clearInterval(this.intervalId);
    this.intervalId = window.setInterval(this.zoomToPercentileInterval.bind(this), this.interval);
    console.info("Setting Percentile interval to ", configuredInterval, "ms from ", this.interval, "ms ");
    this.interval = configuredInterval;
  }
  this.zoomToPercentile();
}

/**
 * Ensure the last point is always in view of the series
 */
ChartingPercentile.prototype.zoomLastPoint = function () {
  var axes = this.chart.getAllYAxes();
  var index;
  var seriesIndex;
  for (index = 0; index < axes.length; index++) {
    var axis = axes[index];
    var lastAxisPoints = [];
    var percentileExtremes = axis.options.percentileExtremes;
    //recalculate the extremes if missing, forcing the last point in
    if (!percentileExtremes) {
      //no need to do anything..it should be in view
      return;
    }

    for (seriesIndex = 0; seriesIndex < axis.series.length; seriesIndex++) {
      var series = axis.series[seriesIndex];
      if (series.yData && series.yData.length > 0) {
        lastAxisPoints.push(series.yData[series.yData.length - 1]);
      }
    }
    if (this.recalculateExtremes(percentileExtremes, lastAxisPoints)) {
      this.setExtremes(axis, percentileExtremes);
    }

  }
}

ChartingPercentile.prototype.recalculateExtremes = function (extremes, lastAxisPoints) {
  var newMin = extremes.min;
  var newMax = extremes.max;
  var maxDelta = 0;
  for (var pointIndex = 0; pointIndex < lastAxisPoints.length; pointIndex++) {
    var lastAxisValue = lastAxisPoints[pointIndex];
    var points = [];
    if ( Array.isArray( lastAxisValue ) ) {
      Array.prototype.push.apply( points, lastAxisValue );
    } else {
      points.push( lastAxisValue );
    }
    points.forEach(function( point ) {
      if ( point == null ) {
        return;
      }
      if (point < newMin) {
        newMin = point;
      } else if (point > newMax) {
        newMax = point;
      }
    });

  }
  if (newMin != extremes.min || newMax != extremes.max) {
    var delta = ( newMax - newMin ) * this.getExpansionRatio() / 2;
    if (newMax != extremes.max) {
      extremes.max = newMax + delta;
    } else {
      extremes.min = newMin - delta;
    }
    console.info("Quick Recalc of extremes to include last points min ", extremes.min, " max ", extremes.max);
    return true;
  }
  return false;
}

ChartingPercentile.prototype.zoomToPercentile = function (xStart, xEnd) {

  var axes = this.chart.getAllYAxes();
  var index;
  for (index = 0; index < axes.length; index++) {
    this.setPercentileExtremes(axes[index], null, xStart, xEnd);
  }
  this.chart.redraw();
};

ChartingPercentile.prototype.getExpansionRatio = function () {
  return this.chart.getOptionParameter(KEY_PERCENTILE_EXPANSION);
}

ChartingPercentile.prototype.getDeflectionAmount = function (min, max) {
  return ( ( max - min ) * this.getExpansionRatio() ) / 2;
}


ChartingPercentile.prototype.resolveData = function ( incomingData ) {
  var self = this;
  var allData = [];
  if ( incomingData == null || incomingData.length == 0 ) {
    return allData;
  }
  //not a special data type
  if ( Array.isArray ( incomingData[ 0 ]) == false ) {
    return incomingData;
  }
  //all others get averaged
  incomingData.forEach( function( point ) {
    Array.prototype.push.apply( allData, self.getPointValue( point ) )
  });

  return allData;
}

ChartingPercentile.prototype.calculatePercentile = function (incomingData, preservedData, pointsToKeep) {
  var self = this;
  var percentile;
  var removalEndPercentage;
  var index;
  var returnValue;
  var pointsToRemove = 0;

  if (!isDefined(pointsToKeep)) pointsToKeep = 0;
  if (!isDefined( preservedData )) preservedData = [];

  percentile = this.chart.getOptionParameter(KEY_PERCENTILE);
  if ( incomingData.length == 0 || percentile > 1 ) return {
    min: null,
    max: null
  }
  var allData = self.resolveData( incomingData );

  allData.sort(function (a, b) {
    return a - b;
  });
  removalEndPercentage = ( 1 - percentile ) / 2;
  if (removalEndPercentage > 0)  pointsToRemove = ~~(allData.length * removalEndPercentage);

  var topValue = allData[allData.length - pointsToRemove - 1];
  var bottomValue = allData[pointsToRemove];
  if (pointsToKeep > 0) {
    for (index = 0; index < preservedData.length; index++) {
      var preservedPoint = preservedData[index];
      if (preservedPoint > topValue) topValue = preservedPoint;
      if (preservedPoint < bottomValue) bottomValue = preservedPoint;
    }
  }

  var deflection = this.getDeflectionAmount(bottomValue, topValue);

  returnValue = {
    min: bottomValue == null ? null : bottomValue - deflection,
    max: topValue == null ? null : topValue + deflection
  }

  console.log("Percentile Calculation, Percentile: ", percentile,
    " Expansion: ", this.getExpansionRatio(),
    " Points Evaluated: ", allData.length,
    "Points to Remove: ", pointsToRemove,
    "Points preserved: ", preservedData.length,
    "Bounds:", returnValue);

  return returnValue;
}


ChartingPercentile.prototype.getPercentileExtremes = function (yAxis, keepOverride, xMin, xMax) {
  var index;
  var allData = [];
  var preservedData = [];
  var currentSeries;
  var returnValue = null;
  var percentile;
  var expansionRatio;
  var removalEndPercentage;
  var pointsToRemove = 0;
  var pointsToKeep = 0;
  var extremes = yAxis.chart.xAxis[0].getExtremes();
  var xStart = isValued(xMin) ? xMin : ( isValued(extremes.userMin) ? extremes.userMin : Number.MIN_SAFE_INTEGER );
  var xEnd = isValued(xMax) ? xMax : ( isValued(extremes.userMax) ? extremes.userMax : Number.MAX_SAFE_INTEGER );

  if (!yAxis.series) return returnValue;

  pointsToKeep = this.chart.getOptionParameter(KEY_PERCENTILE_KEEP);
  if (isDefined(keepOverride) && keepOverride != null) {
    pointsToKeep = keepOverride;
  }

  if (yAxis.chart.isManuallyZoomed()) {
    console.log("Manually Zoomed, setting keep points from ", pointsToKeep, " to 0 ");
    pointsToKeep = 0;
  }
  for (index = 0; index < yAxis.series.length; index++) {
    currentSeries = yAxis.series[index];

    if (!isValued(currentSeries.xData) || !isValued(currentSeries.yData)) continue;
    var seriesData = currentSeries.getPointsWithinX(currentSeries, xStart, xEnd);
    seriesData = this.resolveData( seriesData );
    // allData.push.apply(allData, seriesData);
    allData.extend(seriesData);
    if (pointsToKeep > 0) {
      preservedData.push.apply(preservedData, seriesData.slice(seriesData.length - pointsToKeep - 1));
    }
  }

  percentile = this.chart.getOptionParameter(KEY_PERCENTILE);
  if (allData.length == 0 || percentile >= 1) return {
    min: null,
    max: null
  }

  allData.sort(function (a, b) {
    return a - b;
  });

  expansionRatio = this.getExpansionRatio();
  removalEndPercentage = ( 1 - percentile ) / 2;
  if (removalEndPercentage > 0)  pointsToRemove = ~~(allData.length * removalEndPercentage);

  var topValue = allData[allData.length - pointsToRemove - 1];
  var bottomValue = allData[pointsToRemove];
  if (pointsToKeep > 0) {
    for (index = 0; index < preservedData.length; index++) {
      var preservedPoint = preservedData[index];
      if (preservedPoint > topValue) topValue = preservedPoint;
      if (preservedPoint < bottomValue) bottomValue = preservedPoint;
    }
  }

  var deflection = this.getDeflectionAmount(bottomValue, topValue);

  returnValue = {
    min: bottomValue == null ? null : bottomValue - deflection,
    max: topValue == null ? null : topValue + deflection
  }
  this.lastMax = returnValue.max;
  this.lastMin = returnValue.min;

  console.log("Percentile Calculation, Percentile: ", percentile,
    " Expansion: ", expansionRatio,
    " Points Evaluated: ", allData.length,
    "Points to Remove: ", pointsToRemove,
    "Points preserved: ", preservedData.length,
    "Bounds:", returnValue);

  return returnValue;
}

ChartingPercentile.prototype.setPercentileExtremes = function (yAxis, keepOverride, xStart, xEnd) {
  var extremes = this.getPercentileExtremes(yAxis, keepOverride, xStart, xEnd);
  this.setExtremes(yAxis, extremes, false);
}

ChartingPercentile.prototype.setExtremes = function (axis, extremes, render) {
  axis.chart.setAxisExtremes(axis, extremes.min, extremes.max, render);
  axis.options.percentileExtremes = extremes;
}
